#set( $symbol_pound = '#' )
#set( $symbol_dollar = '$' )
#set( $symbol_escape = '\' )
package ${package}.config;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
import org.springframework.http.HttpStatus;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.filter.OncePerRequestFilter;
import uyun.bird.tenant.api.exception.TenantException;
import ${package}.common.ApiSupport;
import ${package}.common.CurrentProfile;
import ${package}.common.ResultUtil;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

import static org.springframework.core.Ordered.HIGHEST_PRECEDENCE;

/**
 * @author: yangfei
 * @desc: api调用类型区分，进行初步安全验证
 * @date: created in 2019/4/03 11:09
 * @modifed by:
 */
@Configuration
@Slf4j
@Order(HIGHEST_PRECEDENCE)
public class ApiFilter extends OncePerRequestFilter {

    private static String encoding = "UTF-8";

    @Autowired
    private AppProperties appProperties;

    @Autowired
    private RestTemplate restTemplate;

    @Autowired
    private ApiSupport apiSupport;

    @Autowired
    private CurrentProfile currentProfile;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        request.setCharacterEncoding(encoding);

        String url = request.getRequestURI();
        log.warn("url {}", url);
        if (url.equalsIgnoreCase("/") || url.indexOf("/favicon.ico") != -1 || url.indexOf("/static") != -1 || url.indexOf("/tenant") != -1
                || url.indexOf("/notify") != -1 || url.indexOf("/frontend") != -1 || url.indexOf("/h2") != -1 || url.indexOf("/actuator/health") != -1) {
            log.warn("go index");
            filterChain.doFilter(request, response);
        } else {
            log.warn("go other");
            try {
                boolean ap = ApiValidation(request, response, appProperties.getList().split(","));
                if (ap || request.getRequestURI().equalsIgnoreCase("/")) {
                    filterChain.doFilter(request, response);
                }
            } catch (Exception e) {
                log.error("ApiValidation is error {}", e);
                send(response, HttpStatus.INTERNAL_SERVER_ERROR);
            }
        }

    }

    public boolean ApiValidation(HttpServletRequest request, HttpServletResponse response, String[] hostList) {
        final String requestURI = request.getRequestURI();

        //如果请求地址包含frontapi
        if (requestURI.contains(APIType.FRONTAPI.name().toLowerCase())) {
            log.debug("request form type is frontapi");
            if (!isComeFormGateway(request)) {
                log.debug("isComeFormGateway");
                send(response, HttpStatus.FORBIDDEN);
                return false;
            }
            if (!isValidToken(request)) {
                log.debug("isValidToken");
                send(response, HttpStatus.UNAUTHORIZED);
                return false;
            } else {
                String userId = checkToken(request);
                if (userId == null) {
                    log.debug("userId: {}", userId);
                    send(response, HttpStatus.UNAUTHORIZED);
                    return false;
                }
            }
        }
        //如果请求地址包含openapi，合法性有security判断，避免重复判断带来压力
        else if (requestURI.contains(APIType.OPENAPI.name().toLowerCase())) {
            log.debug("request form type is openapi");
            if (!isValidApikey(request)) {
                log.debug("isValidApikey");
                send(response, HttpStatus.UNAUTHORIZED);
                return false;
            }
        } else {
            log.debug("not front or open api");
            send(response, HttpStatus.FORBIDDEN);
            return false;
        }

        //判断请求接口的远程IP合法性
        if (!isValidAddr(request, hostList)) {
            log.debug("isValidAddr");
            send(response, HttpStatus.FORBIDDEN);
            return false;
        }
        return true;
    }

    //判断请求是否来自gateway
    public boolean isComeFormGateway(HttpServletRequest request) {

        if (currentProfile.getProfile().equalsIgnoreCase("dev")) {
            return true;
        }

        final String header = request.getHeader("X-From-Gateway");
        log.debug("header:{}", header);


        if (null == header || Boolean.valueOf(header) == false) {
            return false;
        }
        return true;
    }

    //判断token是否存在，合法性有security判断，避免重复判断带来压力
    public static boolean isValidToken(HttpServletRequest request) {
        final String token = getCookie(request, "token");
        if (StringUtils.isEmpty(token)) {
            return false;
        }

        return true;
    }

    private String checkToken(HttpServletRequest request) {
        final String token = getCookie(request, "token");
        try {
            return apiSupport.checkToken(token);
        } catch (TenantException e) {
            if (e.getErrorCode() == 401) {
                return null;
            } else {
                throw e;
            }
        }
    }

    //判断apikey是否合法
    public static boolean isValidApikey(HttpServletRequest request) {
        final String apikey = null != request.getParameter("apikey") ? request.getParameter("apikey") : request
                .getParameter("api_key");
        if (StringUtils.isEmpty(apikey)) {
            return false;
        }

        return true;
    }

    //判断RemoteAddr是否存在于Apollo的common配置文件的host.list属性之中
    public boolean isValidAddr(HttpServletRequest request, String[] hostList) {

        //dev 情况跳过ip验证
        if (currentProfile.getProfile().equalsIgnoreCase("dev")) {
            return true;
        }

        Set<String> remoteIps = getRemoteIps(request);
        if (null == hostList || hostList.length == 0) {
            logger.error("Config center host.list is empty");
            return false;
        }

        for (String host : hostList) {
            if (remoteIps.contains(host)) {
                return true;
            }
        }

        return false;
    }

    /**
     * 根据指定名称获取Cookie
     *
     * @param request
     * @param cookieName
     * @return
     */
    public static String getCookie(HttpServletRequest request, String cookieName) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals(cookieName)) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }


    /**
     * 发送错误信息
     *
     * @param response
     * @param httpStatus
     */
    public static void send(HttpServletResponse response, HttpStatus httpStatus) {
        response.setCharacterEncoding(encoding);
        String json = JSON.toJSONString(ResultUtil.error(String.valueOf(httpStatus.value()), httpStatus.getReasonPhrase()));
        log.warn("response {}", json);
        try {
            response.setStatus(httpStatus.value());
            response.getOutputStream().write(json.getBytes());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    private Set<String> getRemoteIps(HttpServletRequest request) {
        List<String> keys = Arrays.asList("x-forwarded-for",
                "Proxy-Client-IP",
                "WL-Proxy-Client-IP",
                "HTTP_CLIENT_IP",
                "HTTP_X_FORWARDED_FOR",
                "X-Real-IP",
                "X-Forwarded-For");
        Set<String> ips = keys.stream()
                .map(request::getHeader)
                .filter(StringUtils::isNotBlank)
                .filter(t -> !"unknown".equalsIgnoreCase(t))
                .map(t -> t.split(","))
                .flatMap(Arrays::stream)
                .collect(Collectors.toSet());
        ips.add(request.getRemoteHost());
        return ips;

    }

    public enum APIType {

        FRONTAPI, OPENAPI
    }
}
